
--------------------------------------------------------------------------
-- 运营活动 幸运方块
--#author:zhaoyongbo
--time:2021-08-27
--
--------------------------------------------------------------------------

-- 文件
local gg         = require("modules.business_welfare.welfare_gg")
local config     = require("modules.business_welfare.welfare_lua_config")
local uu         = require("modules.commhm.utils")
local ns_account = require("modules.business_welfare.welfare_bridge")
local log        = require("modules.commhm.log")
local code_conf  = require("modules.business_welfare.welfare_code")
local op_log     = require("modules.business_welfare.welfare_op_log")
local g_enum     = require("modules.business_welfare.welfare_enum")
local bridge     = require("modules.business_common.business_account")
local misc       = require("modules.business_common.business_utils")


-- 变量 开头定义优先级 系统库 > ngx > 自定义
local pairs      = pairs
local ipairs     = ipairs
local math       = math
local next       = next
local table      = table
local tonumber   = tonumber

local ngx = ngx

local error_code = code_conf.error_code
local account = ns_account.private_account

local FOO = {
    _VERSION = '0.12.1',
    V1 = {},    -- 旧版需求特性化接口
    V2 = {},    -- v2优化版特性化接口
}

FOO.BUFFER_OF = {
    RESIST_MONKEY = 1,  -- 安吉洛思：防御猴子
    DOUBLE_REWARD = 2,  -- 嘟嘟啵啵：双倍奖励
}


-- ! @ 进入类型枚举
FOO.ENTER_BY = {
    WATCH_ADVERT = 1,
    USE_ENERGY   = 2,
}


--! @ 进入类型
FOO.ENTER_TYPE_MAP = {
    [FOO.ENTER_BY.WATCH_ADVERT] = 'watch_the_video',
    [FOO.ENTER_BY.USE_ENERGY]   = 'use_item',
}


--! @ 复活类型枚举
FOO.REVIVE_FROM = {
    WATCH_ADVERT = 1,       -- 观看广告复活
    USE_ITEM     = 2,       -- 使用物品复活
}


--! @ 复活类型
FOO.REVIVE_OPS = {
    [FOO.REVIVE_FROM.WATCH_ADVERT] = 'watch_the_video',        --看广告
    [FOO.REVIVE_FROM.USE_ITEM]     = 'use_item',               --使用迷你豆
}


--! @ 任务事件
FOO.TASK_EVENT = {
    ENTER  = 'blind_box_join',  -- 参与幸运盲盒活动
    FINISH = 'blind_box_run',   -- 通关幸运盲盒活动
    RESIST = 'blind_box_repel', -- 成功赶跑小顽猴
}


FOO.task_report = function ( event )
    return bridge.report_task_event({ user_action = event })
end


--! @ 抽中的物品类型
FOO.PICK_TYPE = g_enum.lucky_block.pick_type
--     monkey = 1,         --抽中猴子
--     item = 2,           --抽中物品
--     buffer = 3,         --抽中祝福
--     resist_monkey = 4,   --抽中猴子并且拥有祝福


--! @ 开发标志位，开发完成后删除
FOO.TEST = false


--获取配置
FOO.get_cfg = function()
    if FOO.TEST then
        return {
            activity_switch = 1,			--	活动开关
            version_min= '',                --  最低版本
            v2_limit = '2.0.0',             --  v2最低版本要求
            max_watch_ad_times = 10,        --  最大广告次数(进入)
            max_resurrection_times = 10,    --  v1_最大复活次数,公用
            useitem_resurrection_times = 3, --  v2_使用道具复活次数
            watchad_resurrection_times = 3, --  v2_观看广告复活次数
		    max_energy = 10,	  		    --  最多能获得多少体力（体力值上限）
		    energy_flush_time = 600, 	    --  体力刷新时间单位秒（体力值刷新间隔）
		    monkey_rate = 100,			    --  第一次抽中猴子的概率（千分比）
            up_monkey_rate = 100,		    --  每过一关猴子的概率上升多少 （千分比）
            max_monkey_rate = 800,		    --  猴子的概率上限 （千分比）
            item_lib= {					    --  v1版本物品库
                {
                    id = 20032,
                    num = 1,
                    rate = 50,
                },
                {
                    id = 1,             -- 祝福id
                    num = 1,
                    rate = 500,
                },
                {
                    id = 20036,
                    num = 1,
                    rate = 500,
                },
                {
                    id = 26025,
                    num = 1,
                    rate = 500,
                },
            },
            activity = {
                [1] = {
                    id = 1,
                    start_time = 1609430400,
                    end_time = 1640966400,
                    rewards = {
                        {
                            id = 20032,
                            num = 1,
                            rate = 50,
                        },
                        {
                            id = 10053,             -- 祝福id
                            num = 1,
                            rate = 500,
                        },
                        {
                            id = 20036,
                            num = 1,
                            rate = 500,
                        },
                        {
                            id = 26025,
                            num = 1,
                            rate = 500,
                        },
                        {
                            id = 10054,
                            num = 1,
                            rate = 500,
                        }
                    }
                },
                [2] = {
                    id = 2,
                    start_time = 1640966400,
                    end_time = 1641225600,
                    rewards = {
                        {
                            id = 20032,
                            num = 1,
                            rate = 50,
                        },
                        {
                            id = 10053,             -- 祝福id
                            num = 1,
                            rate = 500,
                        },
                        {
                            id = 20036,
                            num = 1,
                            rate = 500,
                        },
                        {
                            id = 10054,
                            num = 1,
                            rate = 500,
                        }
                    }
                },
            },
            buffers = {
                [FOO.BUFFER_OF.RESIST_MONKEY] = {buffid = FOO.BUFFER_OF.RESIST_MONKEY, itemid = 10053},  -- 安吉洛思
                [FOO.BUFFER_OF.DOUBLE_REWARD] = {buffid = FOO.BUFFER_OF.DOUBLE_REWARD, itemid = 10057},  -- 嘟嘟啵啵
            },
            bufferitems = {
                [10053] = {buffid = FOO.BUFFER_OF.RESIST_MONKEY, itemid = 10053},  -- 安吉洛思
                [10057] = {buffid = FOO.BUFFER_OF.DOUBLE_REWARD, itemid = 10057},  -- 嘟嘟啵啵
            },
            max_layer = 100,            -- 最大关卡
            resurrection_item_id  = 1,  -- 复活消耗物品id （迷你豆）
            resurrection_item_num = 2,  -- 复活消耗物品数量
        }
    else
        return config.get_cfg("lucky_block")
    end
end


--! @ 配置检查
FOO.check_conf = function(cfg)
    -- 检查逻辑移动至 wekfare_config_check 检查
    if not cfg or cfg.activity_switch == 0 then
        return false
    end
    return true
end


--! @ 开始闯关参数检查
FOO.enter_param_check = function(enter_type)
    -- 1 检查参数类型合法性
    if not FOO.ENTER_TYPE_MAP[enter_type] then
        return false
    end
    return true
end


--! @ 获取当前赛季信息
FOO.curr_activity = function (cfg)
    local now = uu.now()
    for _, activity in pairs(cfg.activity or {}) do
        if now >= activity.start_time and now < activity.end_time then
            return activity
        end
    end
    return nil
end


FOO.activity_init = function (cfg)
    local now = uu.now()
    local new_activity = FOO.curr_activity(cfg)
    if new_activity then
        return new_activity.id
    end

    -- 当前无活动则获取下一个活动
    for _, activity in pairs(cfg.activity or {}) do
        if now < activity.start_time then
            new_activity = new_activity or activity
            new_activity = activity.start_time >= new_activity.start_time and new_activity or activity
        end
    end

    return new_activity and new_activity.id or nil
end


--! @ 刷新玩家广告次数
FOO.flush_day_times = function(cfg, data)
    -- 初始化和 跨天重置次数
    local now = uu.now()
    if not data.last_flush_times
    or not uu.compareTimes(now, data.last_flush_times) then
        data.last_flush_times = now
        data.play_times = 0     --每天完的总次数
        data.watch_times = 0    --看广告次数
    end
end


--! @ V1版本检查复活条件
FOO.V1.check_resurrection = function(cfg, data, op)
    FOO.flush_day_times(cfg, data)

    -- 复活次数检查
    local watchad_resurrection = data.resurrection_times or 0
    local useitem_resurrection = data.useitem_revive_cnt or 0
    if (watchad_resurrection + useitem_resurrection) >= cfg.max_resurrection_times then
        return false, error_code.ERROR_NOT_ALIVE
    end

    -- 道具扣除检查
    if  FOO.REVIVE_FROM.USE_ITEM == op
    and not account.item_remove(cfg.resurrection_item_id, cfg.resurrection_item_num, "lucky_block_resurrection") then
        log.loggerdebug("lucky block del monkey item error")
        return false, error_code.ERROR_DEL_ITEM
    end

    return true
end


--! @ V2版本检查复活条件
FOO.V2.check_resurrection = function(cfg, data, op)
    FOO.flush_day_times(cfg, data)

    if FOO.REVIVE_FROM.WATCH_ADVERT == op then
        -- 广告复活次数检查
        if (data.resurrection_times or 0) >= cfg.watchad_resurrection_times then
            return false, error_code.ERROR_NOT_ALIVE
        end

        return true
    elseif FOO.REVIVE_FROM.USE_ITEM == op then
        -- 道具复活次数检查
        if (data.useitem_revive_cnt or 0) >= cfg.useitem_resurrection_times then
            return false, error_code.ERROR_NOT_ALIVE
        end

        -- 道具扣除检查
        if  FOO.REVIVE_FROM.USE_ITEM == op
        and not account.item_remove(cfg.resurrection_item_id, cfg.resurrection_item_num, "lucky_block_resurrection") then
            log.loggerdebug("lucky block del monkey item error")
            return false, error_code.ERROR_DEL_ITEM
        end

        return true
    end
    return false, error_code.ERROR_PARAM
end


--! @ 每日参与次数检查
FOO.check_ad_enter_times = function(cfg, data)
    FOO.flush_day_times(cfg, data)
    if data.watch_times < cfg.max_watch_ad_times then
        return true
    end
    return false
end


--! @ 刷新体力
FOO.flush_energy = function(cfg, data)
    local now = uu.now()
    if not data.last_flush_time then
        data.last_flush_time = now
        data.energy = cfg.max_energy
        return
    end
    local pass_time = now - data.last_flush_time
    if pass_time < cfg.energy_flush_time then
        return
    end

    -- 分为2种情况， 体力满了，体力没满 计算时间应该不一样
    local add_energy = math.floor(pass_time / cfg.energy_flush_time)
    if (add_energy + data.energy) >= cfg.max_energy then
        data.last_flush_time = now
        data.energy = cfg.max_energy
        return
    else
        data.last_flush_time = data.last_flush_time + add_energy * cfg.energy_flush_time
        data.energy = data.energy + add_energy
        return
    end
end


--! @ 消耗体力检查
FOO.check_energy = function(cfg, data)
    FOO.flush_energy(cfg, data)
    if data.energy < 1 then
        return false
    end
    return true
end


FOO.layer_finish = function (cfg, data)
    return cfg.max_layer < (data.layer or 1)
end


--! @ 最大关卡检查
FOO.check_max_layer = function(cfg, data)
    return not FOO.layer_finish(cfg, data) and true or false
end


--! @ 检查是否有猴子
FOO.check_monkey = function(data)
    return data.is_monkey
end


--! @ 赛季清理， 部分玩家切换赛季，没有奖励，需要直接帮忙重置
FOO.clear_season = function(cfg, data)
    if not misc.check_min_version(cfg.v2_limit) then
        return
    end
    local curr_activity = FOO.curr_activity(cfg)
    if not curr_activity or data.activity == curr_activity.id then
        return
    end
    if not next(data.items or {}) then
        FOO.reset_layer(cfg, data)
    end
end


FOO.check_activity_enter = function (cfg, data)
    -- 低版本进入无限制
    if not misc.check_min_version(cfg.v2_limit) then
        return true
    end

    -- 当前活动未开启
    local curr_activity = FOO.curr_activity(cfg)
    if not curr_activity then
        return false, error_code.ERROR_ACTIVITY_CLOSE
    end

    -- 活动赛季修正
    data.activity = curr_activity.id
    return true
end


--! @ 点击闯关
--! @ args > type : 1,看视频 2，使用体力
FOO.lucky_block_enter = function()
    local enter_type = tonumber(ngx.ctx.m_params.type) or 0
    if not FOO.enter_param_check(enter_type) then
        return error_code.ERROR_PARAM
    end

    local cfg = FOO.get_cfg()
    if not FOO.check_conf(cfg) then
        return error_code.ERROR_CONFIG
    end

    local userdata = gg.get_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK) or {}
	if not next(userdata) then
		return error_code.ERROR_NO_DATA
	end

    --进入次数检查
    if enter_type == FOO.ENTER_BY.WATCH_ADVERT
    and not FOO.check_ad_enter_times(cfg, userdata) then
        return error_code.ERROR_NOT_TIMES
    end

    -- 消耗体力检查
    if enter_type == FOO.ENTER_BY.USE_ENERGY
    and not FOO.check_energy(cfg, userdata) then
        return error_code.ERROR_NOT_ENERGY
    end

    --检查最大关卡
    if not FOO.check_max_layer(cfg, userdata) then
        return error_code.ERROR_MAX_LAYER
    end

    --检查猴子，如果上次抽中的是猴子
    if FOO.check_monkey(userdata) then
        return error_code.ERROR_HAD_MONKEY
    end

    --检查开始关卡
    if tonumber(userdata.layer) ~= 0 then
        return error_code.ERROR_NOT_ENERGY
    end

    --赛季检查
    local succ, reason = FOO.check_activity_enter(cfg, userdata)
    if not succ then
        return reason
    end

    return FOO.do_enter(cfg, userdata, enter_type)
end


FOO.do_enter = function(cfg, data, enter_type)
    if enter_type == FOO.ENTER_BY.USE_ENERGY then
        --当前是满体力的时候， 消耗后应该刷新时间
        if data.energy == cfg.max_energy then
            data.last_flush_time = uu.now()
        end
        data.energy = data.energy - 1
    elseif enter_type == FOO.ENTER_BY.WATCH_ADVERT then
        data.watch_times = data.watch_times + 1
    end

    data.play_times = (data.play_times or 0) + 1
    data.layer = (data.layer or 0) + 1

    gg.set_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK, data)

    FOO.task_report(FOO.TASK_EVENT.ENTER)
    FOO.write_oplog(enter_type, data)

    local last_flush_time = (data.energy == cfg.max_energy) and 0 or (data.last_flush_time + cfg.energy_flush_time)
    return error_code.COMMON_SUCCESS, {
        last_flush_time = last_flush_time,
        energy = data.energy
    }
end


FOO.check_activity_start = function (cfg, userdata)
    -- 低版本客户端无活动时间限定
    if not misc.check_min_version(cfg.v2_limit) then
        -- 低版本客户端高版本数据
        if userdata.activity then
            return false, error_code.ERROR_CLIENT_VERSION
        end
        return true
    end

    -- 高版本客户端和低版本数据
    if not userdata.activity then
        return false, error_code.ERROR_ACTIVITY_RESET
    end

    -- 当前不在活动时间内
    local activity = FOO.curr_activity(cfg)
    if not activity or activity.id ~= userdata.activity then
        return false, error_code.ERROR_ACTIVITY_RESET
    end
    return true
end


--! @ 开始闯关
FOO.lucky_block_start = function()
    local cfg = FOO.get_cfg()
    if not FOO.check_conf(cfg) then
        return error_code.ERROR_CONFIG
    end

    local userdata = gg.get_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK) or {}
	if not next(userdata) then
		return error_code.ERROR_NO_DATA
	end

    -- 关卡检查检查
    if not userdata.layer or userdata.layer <= 0  then
        return error_code.ERROR_NOT_ENERGY
    end

    --检查最大关卡
    if not FOO.check_max_layer(cfg, userdata) then
        return error_code.ERROR_MAX_LAYER
    end

    --检查猴子，如果上次抽中的是猴子
    if FOO.check_monkey(userdata) then
        return error_code.ERROR_HAD_MONKEY
    end

    -- 版本检查
    local succ, reason = FOO.check_activity_start(cfg, userdata)
    if not succ then
        return reason
    end

    local ret, info = FOO.do_start(cfg, userdata)
    if ret ~= error_code.COMMON_SUCCESS then
        return ret
    end

    --保持数据
    gg.set_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK, userdata)

    return ret, info
end


-- 查询已拥有的祝福对应的itemid
FOO.owned_buff_items = function (cfg, data)
    local items = {}
    for buffid, _ in pairs(data.buffers or {}) do
        if cfg.buffers[buffid] then
            local buffer = cfg.buffers[buffid]
            items[buffer.itemid] = buffer or nil
        end
    end
    return items
end


-- 是否随机遇上了猴子（暂定千分比）
local encounter_monkey = function (cfg, data)
    local monkey_rate = cfg.monkey_rate + (data.layer-1) * cfg.up_monkey_rate
    return math.min(monkey_rate, cfg.max_monkey_rate) > math.random(1, 1000)
end


FOO.do_start = function (cfg, data)
    local item = nil
    if data.layer % g_enum.lucky_block.reward_layer == 0 then
        -- 5、10、15... 必得非祝福物品
        item = FOO.get_random_award(cfg, data, cfg.bufferitems)
    elseif not encounter_monkey(cfg, data) then
        -- 没遭遇猴子随机一个物品(已拥有的buff除外)
        item = FOO.get_random_award(cfg, data, FOO.owned_buff_items(cfg, data))
    elseif not data.is_alive then
        -- 遭遇猴子但是没护盾直接返回
        data.is_monkey = true
        return FOO.save_data_and_return(cfg, data, FOO.PICK_TYPE.monkey)
    else
        --  遭遇猴子,用护盾抵消了猴子,然后随机获得非祝福物品
        data.is_alive = nil
        item = FOO.get_random_award(cfg, data, cfg.bufferitems)
        FOO.combine_items(data, item)
        FOO.task_report(FOO.TASK_EVENT.RESIST)
        return FOO.save_data_and_return(cfg, data, FOO.PICK_TYPE.resist_monkey, {item = item})
    end

    -- 护盾生仅一关有效
    data.is_alive = nil
    if uu.tableIsEmpty(item) then
        return error_code.COMMON_ERROR;
    end

    -- 抽中的是祝福buffer
    if cfg.bufferitems[item.id] then
        local buffid = cfg.bufferitems[item.id].bufid
        if FOO.BUFFER_OF.RESIST_MONKEY == buffid then
            data.is_alive = true
        else
            data.buffers = data.buffers or {}
            data.buffers[buffid] = (data.buffers[buffid] or 0) + 1
        end
        return FOO.save_data_and_return(cfg, data, FOO.PICK_TYPE.buffer, {item = item})
    end

    local buff = {} -- 是否有双倍奖励buff
    if data.buffers and data.buffers[FOO.BUFFER_OF.DOUBLE_REWARD] then
        item.num = item.num * 2
        buff[#buff + 1] = FOO.BUFFER_OF.DOUBLE_REWARD
        data.buffers[FOO.BUFFER_OF.DOUBLE_REWARD] = nil
        log.debug("double reward result: "..uu.to_str(item))
    end

    FOO.combine_items(data, item)
    return FOO.save_data_and_return(cfg, data, FOO.PICK_TYPE.item, {buff = buff, item = item})
end


--从物品池中抽取一个物品
FOO.random_items = function(pool, excepted)
    excepted = excepted or {}
    -- 1. 计算总权重
    local rate_total = 0
    for _, item in pairs(pool or {}) do
        if not excepted[item.id] then
            rate_total = rate_total + (item.rate or 0)
        end
    end

    if rate_total > 0 then
        -- 2. 从池中捞取一个物品
        local rate_count = 0
        local rate_value = math.random(1, rate_total)
        for idx, item in pairs(pool or {}) do
            if not excepted[item.id] then
                rate_count = rate_count + (item.rate or 0)
                if rate_count >= rate_value then
                    return uu.table_value_copy(pool[idx])
                end
            end
        end
    end

    log.error('got no item'..uu.to_str({pool = pool, excepted = excepted}))
    return {}
end


--! @ 合并奖励
FOO.combine_items = function(data, item)
    if not (item and item.id and item.num) then
        return
    end
    data.items = data.items or {}
    data.items[item.id] = data.items[item.id] or { id = item.id, num = 0 }
    data.items[item.id].num = (data.items[item.id].num or 0) + item.num
end


--! @ 从物品池随机一个不是过滤列表中的奖励
FOO.get_random_award = function (cfg, userdata, excepted)
    -- 根据玩家当前数据决定从新旧的奖池里随机
    local v2 = userdata.activity and cfg.activity[userdata.activity]
    local pool = v2 and cfg.activity[userdata.activity].rewards or cfg.item_lib
    return FOO.random_items(pool, excepted)   -- 随机一个物品
end


--! @ 整理抽盲盒返回数据物品，打日志
FOO.save_data_and_return = function(cfg, data, pick_type, ext)
    ext = ext or {}
    data.layer = (data.layer or 0) + 1
    data.last_pick_type = pick_type --记录上次抽到的是什么类型

    -- 最后一关抽中的不是猴子
    if FOO.layer_finish(cfg, data) and pick_type ~= FOO.PICK_TYPE.monkey then
        FOO.task_report(FOO.TASK_EVENT.FINISH)
    end

    FOO.write_oplog(g_enum.oplog.lucky_block.action.start, data, {item=ext.item, type=pick_type})

    return error_code.COMMON_SUCCESS, {
        pick_type = pick_type,
        item      = ext.item or nil,
        buff      = ext.buff or nil,    -- 当前生效的buff
    }
end


--! @ 领取奖励
FOO.lucky_block_pick_rewards = function()

    local cfg = FOO.get_cfg()
    if not FOO.check_conf(cfg) then
        return error_code.ERROR_CONFIG
    end

    local userdata = gg.get_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK) or {}
	if not next(userdata) then
		return error_code.ERROR_NO_DATA
	end

    --检查猴子，如果上次抽中的是猴子
    if FOO.check_monkey(userdata) then
        return error_code.ERROR_HAD_MONKEY
    end

    FOO.flush_day_times(cfg, userdata)
    FOO.flush_energy(cfg, userdata)

    return FOO.do_rewards(cfg, userdata)
end


--! @ 领取奖励逻辑
FOO.do_rewards = function(cfg, data)
    if not next(data.items or {}) then
        return error_code.ERROR_ADD_ITEM
    end
    local rewards = {}
    for _, item in pairs(data.items or {}) do
        rewards[#rewards + 1] = item
    end

    FOO.reset_layer(cfg, data)

    local ret = account.items_add(rewards, "lucky_block_rewards")
	if not ret then
        log.loggerdebug("lucky block pick item error, ret = %%", ret)
		return error_code.ERROR_ADD_ITEM
	end

    gg.set_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK, data)

    FOO.write_oplog(g_enum.oplog.lucky_block.action.pick, data, {pick_items=rewards})

    return FOO.ret_all_data(cfg, data)
end


--! @ 重置关卡
FOO.reset_layer = function(cfg, data)
    data.buffers = nil
    data.items = {}
    data.layer = 0
    data.is_monkey = nil
    data.is_alive = nil
    data.resurrection_times = 0
    data.useitem_revive_cnt = 0
    data.last_pick_type = nil    --重置上次抽中得类型

    -- 低版本客户端无活动标识
    local v2 = misc.check_min_version(cfg.v2_limit)
    data.activity = v2 and FOO.activity_init(cfg) or nil
end


--! @ 放弃奖励
FOO.destory_reward = function()
    local cfg = FOO.get_cfg()
    if not FOO.check_conf(cfg) then
        return error_code.ERROR_CONFIG
    end

    local userdata = gg.get_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK) or {}
	if not next(userdata) then
		return error_code.ERROR_NO_DATA
	end

    FOO.flush_day_times(cfg, userdata)
    FOO.flush_energy(cfg, userdata)

    FOO.reset_layer(cfg, userdata)

    gg.set_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK, userdata)

    FOO.write_oplog(g_enum.oplog.lucky_block.action.abandon, userdata)
    return FOO.ret_all_data(cfg, userdata)
end


--! @ 消除猴子继续闯关
FOO.lucky_block_delete_monkey = function()
    local op = tonumber(ngx.ctx.m_params.type) or 0
    if not FOO.REVIVE_OPS[op] then
        return error_code.ERROR_PARAM
    end

    local cfg = FOO.get_cfg()
    if not FOO.check_conf(cfg) then
        return error_code.ERROR_CONFIG
    end

    local userdata = gg.get_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK) or {}
	if not next(userdata) then
		return error_code.ERROR_NO_DATA
	end

    -- 检查如果当前不是猴子，不能复活
    if not userdata.is_monkey then
        return error_code.ERROR_PARAM
    end

    -- 复活条件检查
    local V2 = misc.check_min_version(cfg.v2_limit) and userdata.activity
    local check_resurrection_func = V2 and FOO.V2.check_resurrection or FOO.V1.check_resurrection
    local succ, reason = check_resurrection_func(cfg, userdata, op)
    if not succ then
        return reason
    end

    if FOO.REVIVE_FROM.USE_ITEM == op then
        userdata.useitem_revive_cnt = (userdata.useitem_revive_cnt or 0) + 1
    else --if FOO.REVIVE_FROM.WATCH_ADVERT == op then
        userdata.resurrection_times = (userdata.resurrection_times or 0) + 1
    end

    local item = FOO.get_random_award(cfg, userdata, cfg.bufferitems) -- 随机一个非祝福得物品
    FOO.combine_items(userdata, item)-- 合并物品
    userdata.is_monkey = nil
    userdata.last_pick_type = FOO.PICK_TYPE.item
    gg.set_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK, userdata)

    -- 最后一关杀死猴子
    if FOO.layer_finish(cfg, userdata) then
        FOO.task_report(FOO.TASK_EVENT.FINISH)
    end
    FOO.task_report(FOO.TASK_EVENT.RESIST)

    --写日志
    local action = op == FOO.REVIVE_FROM.USE_ITEM
        and g_enum.oplog.lucky_block.action.use_item_r
        or  g_enum.oplog.lucky_block.action.watch_ad_r
    FOO.write_oplog(action, userdata, {
        draw_item = item,
        id  = op == FOO.REVIVE_FROM.USE_ITEM and cfg.resurrection_item_id or nil,
        num = op == FOO.REVIVE_FROM.USE_ITEM and cfg.resurrection_item_num or nil,
    })

    return error_code.COMMON_SUCCESS, {item = item}
end


--! @ 物品兑换体力
FOO.lucky_block_add_energy = function()
    local num = tonumber(ngx.ctx.m_params.num) or 0

    if num <= 0 then
        return error_code.ERROR_PARAM
    end

    local cfg = FOO.get_cfg()
    if not FOO.check_conf(cfg) or not cfg.exchange_item_id then
        return error_code.ERROR_CONFIG
    end

    local userdata = gg.get_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK) or {}
	if not next(userdata) then
		return error_code.ERROR_NO_DATA
	end

    --检查体力值
    if userdata.energy >= cfg.max_energy or userdata.energy + num > cfg.max_energy then
        return error_code.ERROR_FULL_ENERGY
    end

    --扣除物品
    local ret = account.item_remove(cfg.exchange_item_id, cfg.exchange_item_num * num, "lucky_block_exchange")
	if not ret then
        log.loggerdebug("lucky block pick item error, ret = %%", ret)
		return error_code.ERROR_DEL_ITEM
	end

    --添加体力值
    userdata.energy = userdata.energy + num
    gg.set_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK, userdata)

    --日志
    FOO.write_oplog(g_enum.oplog.lucky_block.action.add_energy, userdata, {
        id  = cfg.exchange_item_id,
        num = cfg.exchange_item_num * num
    })

    return error_code.COMMON_SUCCESS, {
        energy = userdata.energy
    }
end


--! @ 进入界面拉取的数据
FOO.lucky_block_data = function()
    -- 获取当前活动配置
    local cfg = FOO.get_cfg()
    if not FOO.check_conf(cfg) then
        return error_code.ERROR_CONFIG
    end

    local userdata = gg.get_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK) or {}
    if not next(userdata) then
        userdata  = {
            layer = 0,
            items = {},
        }
    end

    -- 新数据不能登老端
    local v2 = misc.check_min_version(cfg.v2_limit)
    if not v2 and userdata.activity then
        return error_code.ERROR_CLIENT_VERSION
    end

    FOO.clear_season(cfg, userdata)

    FOO.flush_day_times(cfg, userdata)
    FOO.flush_energy(cfg, userdata)

    gg.set_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK, userdata)

    log.loggerdebug("lucky block all data : data = %%", userdata)
    return FOO.ret_all_data(cfg, userdata)
end


FOO.lucky_block_flush_all = function()
    gg.set_ssdb_cache_info(gg.KEYS.LUCKY_BLOCK, {
        layer = 0,
        items = {}
    })
    return error_code.COMMON_SUCCESS, {}
end


FOO.ret_all_data = function(cfg, data)
    -- 约定体力满的时候返回 0 ， 体力不满的时候返回下一次刷新的时间
    local last_flush_time = (data.energy == cfg.max_energy) and 0 or (data.last_flush_time + cfg.energy_flush_time)
    local resurrection_times = data.resurrection_times or 0
    local useitem_revive_cnt = data.useitem_revive_cnt or 0
    return error_code.COMMON_SUCCESS, {
        layer              = data.layer or 0,               -- 当前多少关
        energy             = data.energy,                   -- 当前体力值
        last_flush_energy_time = last_flush_time,           -- 上次获得体力时间
        buffs              = misc.table2list(data.buffers), -- 当前拥有的祝福(除护盾外)
        items              = data.items or {},              -- 当前拥有的奖励列表
        is_alive           = data.is_alive,                 -- 当前是否已拥有护盾祝福
        watch_ad_times     = data.watch_times,              -- 当日通过广告进入的次数
        last_pick_type     = data.last_pick_type,           -- 上次抽中的类型
        is_monkey          = data.is_monkey,                -- 当前抽中的是猴子
        resurrection_times = data.activity and resurrection_times or resurrection_times + useitem_revive_cnt,        --复活次数
        useitem_revive_cnt = data.activity and (data.useitem_revive_cnt or 0) or nil
    }
end


FOO.write_oplog = function(action, data, log_info)
    if action ~= g_enum.oplog.lucky_block.action.pick then
        return
    end
    local logs = {
            action             = action,
            layer              = data.layer or 0,
            energy             = data.energy,
            owned_items        = data.items or {},
            watch_ad_times     = data.watch_times or 0,
            resurrection_times = data.resurrection_times or 0,
            useitem_revive_cnt = data.useitem_revive_cnt or 0,
    }
    if action == g_enum.oplog.lucky_block.action.start then
        logs.draw_type = log_info.type  --抽中的类型
        logs.draw_item = log_info.item  --抽中的物品
    elseif action == g_enum.oplog.lucky_block.action.pick then
        logs.pick_items = log_info.pick_items
    elseif action == g_enum.oplog.lucky_block.action.watch_ad_r then
        logs.draw_item = log_info.drop_items
    elseif action == g_enum.oplog.lucky_block.action.use_item_r then
        logs.del_item_id = log_info.id
        logs.del_item_num = log_info.num
        logs.draw_item = log_info.drop_items
    elseif action == g_enum.oplog.lucky_block.action.add_energy then
        logs.del_item_id = log_info.id
        logs.del_item_num = log_info.num
    end
    op_log.record("welfare_lucky_block", action, logs)
end


return FOO

